<template>
	<!-- 可视滚动容器 需要设置固定高度 -->
	<div class="pull-scroll-view">
		<!-- touch事件监听容器 -->
		<div class="pull-content"
		     ref="refreshbox"
		     :style="{ 'transform': transform,'transition-duration':transitionDuration}">
			<!-- 下拉刷新状态 -->
			<slot name="pulldown">
				<div v-if="usepulldown"
				     v-show="pullDownStatus>0"
				     class="pull-box-down">
					<i class="iconfont"
					   :class="{'pull-spinner-animation':pullDownStatus==3,'icon-spinner9':pullDownStatus<4,'icon-success':pullDownStatus==4}"
					   :style="{'transform':pullRotate}"></i>
					<span>{{pulldownText}}</span>
				</div>
			</slot>
			<!-- 内容区域 -->
			<div :style="{'min-height':minHeight}">
				<slot></slot>
			</div>
			<!-- 上拉加载状态 -->
			<slot name="pullup">
				<div v-if="usepullup"
				     ref="pullupbox"
				     class="pull-box-up">
					<i v-if="pullUpStatus==3"
					   class="iconfont icon-spinner pull-spinner-animation"></i>
					<span :class="{'nomore':pullUpStatus==4}">{{pullupText}}</span>
				</div>
			</slot>
		</div>
	</div>
</template>
<script>
export default {
  props: {
    // 使用下拉组件
    usepulldown: {
      type: Boolean,
      default: false,
    },
    // 使用上拉组件
    usepullup: {
      type: Boolean,
      default: false,
    },
    // 上拉移动距离临界点
    pullUpDistance: {
      type: Number,
      default: 50,
    },
    // 下拉移动距离临界点
    pullDownDistance: {
      type: Number,
      default: 50,
    },
    // 下拉刷新文字描述数组 顺序对应 pullDownStatus 0 1 2 3 4 五种状态
    pullDownTextArray: {
      type: Array,
      default() {
        return ['下拉刷新', '下拉刷新', '释放刷新', '刷新中……', '刷新成功'];
      },
    },
    // 上拉加载文字描述数组 顺序对应 pullUpStatus 0 1 2 3 4 五种状态
    pullUpTextArray: {
      type: Array,
      default() {
        return ['加载更多', '上拉加载更多', '释放加载', '加载中……', '没有更多了～'];
      },
    },
    // 刷新成功显示时间
    pullDownSuccessDelay: {
      type: Number,
      default: 2000,
    },
  },
  data() {
    return {
      minHeight: 'inherit',
      isLoading: false,
      isMoved: false,
      translate: 0,
      transitionDuration: '0ms',
      // 下拉状态
      pullDownStatus: 0,
      // 上拉状态
      pullUpStatus: 0,
      // touch初始滚动区域高度
      scrollHeight: 0,
      // touch初始可见区域高度
      offsetHeight: 0,
      // 初始touch Y位置
      startTouchY: 0,
      // 初始滚动位置
      startScrollTop: 0,
      // touch移动距离
      touchDistance: 0,
      // 下拉滚动触发中
      pulldownMoving: false,
      // 上拉滚动触发中
      pullupMoving: false,
    };
  },
  mounted() {
    // 设置内容区域最小高度
    this.setContentHeight();
    // 监听touch事件
    this.bindTouchEvents();
  },
  computed: {
    // touch移动距离
    transform() {
      return `translate3d(0, ${this.translate}px, 0)`;
    },
    // 图标转动角度计算
    pullRotate() {
      return (this.pullDownStatus === 4 || this.translate === 0) ? null : `rotate(${this.translate * 2}deg)`;
    },
    // 下拉状态文字描述
    pulldownText() {
      switch (this.pullDownStatus) {
        case 0:// '下拉刷新';
        case 1:// '下拉刷新';
        case 2:// '释放刷新';
        case 3:// '刷新中……';
        case 4:// '刷新成功';
          return this.pullDownTextArray[this.pullDownStatus] || '';
        default:
          return '';
      }
    },
    // 上拉状态文字描述
    pullupText() {
      switch (this.pullUpStatus) {
        case 0:// '加载更多';
        case 1:// '上拉加载更多';
        case 2:// '释放加载';
        case 3:// '加载中……';
        case 4:// '没有更多了～';
          return this.pullUpTextArray[this.pullUpStatus] || '';
        default:
          return '';
      }
    },
  },
  methods: {
    setContentHeight() {
      const oftHeight = this.$el.offsetHeight;
      const pullupdom = this.$refs.pullupbox;
      let min = 0;
      if (pullupdom) {
        min = pullupdom.getBoundingClientRect().height;
      }
      this.minHeight = `${oftHeight - min}px`;
    },
    bindTouchEvents() {
      // touch事件监听
      this.$el.addEventListener('touchstart', this.touchStart);
      this.$el.addEventListener('touchmove', this.touchMove);
      this.$el.addEventListener('touchend', this.touchEnd);
      // 缓动效果完成事件监听 touchend 状态更新移到此处
      this.$refs.refreshbox.addEventListener('transitionend', () => {
        if (this.pullDownStatus < 2) this.pullDownStatus = 0;
        if (this.pullUpStatus < 2) this.pullUpStatus = 0;
      });
    },
    touchStart(ev) {
      if (this.isLoading) {
        return;
      }
      // 状态参数初始化
      this.isMoved = false;
      this.pullDownStatus = 0;
      if (this.pullUpStatus < 4) this.pullUpStatus = 0;
      this.pullupMoving = false;
      this.pulldownMoving = false;
      this.transitionDuration = '0ms';
      const touchobj = ev.changedTouches[0];
      this.scrollHeight = this.$el.scrollHeight;
      this.offsetHeight = this.$el.offsetHeight;
      this.startScrollTop = this.$el.scrollTop;
      this.startTouchY = parseInt(touchobj.clientY, 10);
    },
    touchMove(ev) {
      if (this.isLoading) {
        ev.preventDefault();
        return;
      }
      const moving = () => {
        const touchobj = ev.changedTouches[0];
        // 实时touch Y轴移动距离
        const movingY = parseInt(touchobj.clientY, 10);
        // 实时滚动距离
        const scrollToping = this.$el.scrollTop;
        // touch移动距离计算
        this.touchDistance = this.startTouchY - movingY;
        // 下拉 pulldown判断 （实时滚动距离==0 && touch距离<0）
        if (this.startTouchY < movingY && scrollToping === 0 && this.touchDistance < 0) {
          this.pullupMoving = false;
          this.pulldownMoving = true;
          // 下拉距离计算 touch距离 - 初始 scrollTop 距离
          let downDistance = Math.abs(this.touchDistance - this.startScrollTop);
          // 滑动赋值
          this.translate = downDistance;
          // 状态变更判断
          if (this.usepulldown && downDistance > 0) {
            // 超出设定临界距离 变更状态
            if (downDistance >= this.pullDownDistance) {
              this.pullDownStatus = 2;
            } else {
              this.pullDownStatus = 1;
            }
          }
        } else if (scrollToping === (this.scrollHeight - this.offsetHeight)) {
          // 上拉 pullup （实时滚动距离==(滚动区域高度-可见区域高度)）
          this.pullupMoving = true;
          this.pulldownMoving = false;
          // 上拉距离计算 touch距离 - (实时滚动距离 - 初始 scrollTop 距离)
          let upDistance = this.touchDistance - (scrollToping - this.startScrollTop);
          // 滑动赋值
          this.translate = -upDistance;
          // 状态判断
          if (this.usepullup && this.pullUpStatus < 4 && upDistance > 0) {
            // 超出设定临界距离 变更状态
            if (upDistance >= this.pullUpDistance) {
              this.pullUpStatus = 2;
            } else {
              this.pullUpStatus = 1;
            }
          }
        } else if (this.pulldownMoving) { // 下拉加载触发中 pulldown变pullup
          // 滚动状态回归
          this.pullDownStatus = 1;
        } else if (this.pullupMoving) { // 上拉加载触发中 pullup变pulldown
          // 滚动状态回归
          this.pullUpStatus = 0;
        } else {
          this.translate = 0;
        }
        this.isMoved = true;
      };
        // 去抖调用
      this.throttle(moving(), 20);
    },
    touchEnd() {
      if (this.isLoading || !this.isMoved) {
        this.isMoved = false;
        return;
      }
      this.transitionDuration = '300ms';
      // 下拉刷新处理 处于释放更新状态
      if (this.pullDownStatus === 2) {
        // 更新状态为刷新中
        this.translate = 50;
        this.pullDownStatus = 3;
        this.isLoading = true;
        // 更新上拉状态为非nomore
        this.loadmoreOpen();
        // 滚动到顶部
        this.$el.scrollTop = 0;
        // 分发刷新事件
        this.$emit('on-pulldown-loading');
      } else if (this.pullUpStatus === 2) { // 上拉加载更多 处于释放加载更多状态
        // 更新状态为加载中
        this.translate = 0;
        this.pullUpStatus = 3;
        this.isLoading = true;
        // 滚动到底部
        this.$el.scrollTop = this.scrollHeight - this.offsetHeight;
        // 分发加载更多事件
        this.$emit('on-pullup-loading');
      } else {
        // 复位
        this.translate = 0;
        this.isLoading = false;
      }
      this.isMoved = false;
    },
    // 加载完成
    loadingComplete() {
      this.isLoading = false;
      // 下拉刷新触发中
      if (this.pullDownStatus === 3) {
        // 刷新成功展示
        this.pullDownStatus = 4;
        // 延迟消除刷新成功状态
        setTimeout(() => {
          this.translate = 0;
          this.pullDownStatus = 0;
        }, this.pullDownSuccessDelay);
      }
      // 上拉刷新触发中
      if (this.pullUpStatus === 3) {
        this.translate = 0;
        if (this.pullUpStatus < 4) this.pullUpStatus = 0;
      }
    },
    // 关闭加载更多
    loadmoreClose() {
      this.pullUpStatus = 4;
    },
    // 打开加载更多
    loadmoreOpen() {
      this.pullUpStatus = 0;
    },
    // 去抖函数
    throttle(fn, delay) {
      let allowSample = true;
      return (e) => {
        if (allowSample) {
          allowSample = false;
          setTimeout(() => { allowSample = true; }, delay);
          fn(e);
        }
      };
    },
  },
};
</script>
<style lang="less">
.pull-scroll-view {
	-webkit-overflow-scrolling: touch;
	overflow-x: hidden;
	overflow-y: auto;
}
.pull-content {
	position: relative;
	-webkit-overflow-scrolling: touch;
	-webkit-transform: translate3d(0, 0, 0);
	transform: translate3d(0, 0, 0);
}
.pull-box-down {
	position: absolute;
	top: -50px;
	left: 50%;
	transform: translateX(-50%);
	display: inline-block;
	padding: 10px 0;
	color: #666;
	i {
		display: inline-block;
	}
	span {
		margin-left: 10px;
	}
}
.pull-box-up {
	position: relative;
	left: 50%;
	transform: translateX(-50%);
	bottom: 0;
	display: inline-block;
	color: #666;
	font-size: 14px;
	padding: 10px 0;
	i {
		display: inline-block;
	}
	span {
		margin-left: 10px;
	}
	.nomore {
		color: #999;
	}
}
.pull-spinner-animation {
	animation: pull-spinner 1.2s linear infinite;
}
@keyframes pull-spinner {
	0% {
		transform: rotate(0deg);
	}
	100% {
		transform: rotate(360deg);
	}
}
</style>
